<template>
  <div class="annotator">
    <div class="toolbar">
      <el-switch v-model="editable" active-text="编辑" inactive-text="预览" />
      <el-button v-if="editable" size="small" @click="toggleTextEditMode" :type="textEditMode ? 'primary' : 'default'">
        {{ textEditMode ? '退出文本编辑' : '文本编辑' }}
      </el-button>
      <el-button v-if="editable && textEditMode" size="small" @click="pasteFromClipboard" :icon="DocumentCopy">
        粘贴 (Ctrl+V)
      </el-button>
    </div>
    <div v-if="!editable" class="preview-wrapper">
      <div class="preview-mode-toggle">
        <el-radio-group v-model="previewMode" size="small">
          <el-radio-button label="svg">SVG预览</el-radio-button>
          <el-radio-button label="flow">流式预览</el-radio-button>
        </el-radio-group>
      </div>
      <div v-show="previewMode === 'svg'" class="poplar-wrap">
        <div class="poplar-toolbar">
          <el-button size="small" @click="exportCurrentSvg">导出 SVG</el-button>
          <el-button size="small" @click="exportCurrentPng" :loading="pngExporting">导出 PNG</el-button>
          <el-button size="small" @click="rebuildPoplar">刷新视图</el-button>
        </div>
        <div class="poplar-layout">
          <div ref="gutterRef" class="gutter"></div>
          <div ref="poplarRef" class="poplar-container"></div>
        </div>
      </div>
      <div v-show="previewMode === 'flow'" class="preview-text" @contextmenu.prevent>
        <template v-for="(seg, idx) in rendered" :key="'pv-'+idx">
          <span v-if="seg.type === 'text'">{{ seg.text }}</span>
          <span v-else class="hl" :class="{ multi: seg.multi }" :style="hlStyle(seg)"
            @click="focusAnno(seg.primaryId)">{{ seg.text }}</span>
        </template>
      </div>
    </div>
    <div v-else-if="textEditMode" class="text-edit-mode">
      <el-input ref="textAreaRef" v-model="editableContent" type="textarea" :rows="20" placeholder="请输入或粘贴要标注的文本内容..."
        @keydown="handleKeyDown" @change="updateContent" />
    </div>
    <div v-else class="text" ref="textRef" @mouseup="tryAnnotate">
      <template v-for="(seg, idx) in rendered" :key="idx">
        <span v-if="seg.type === 'text'">{{ seg.text }}</span>
        <span v-else class="hl" :class="{ multi: seg.multi }" :style="hlStyle(seg)" @click="focusAnno(seg.primaryId)"
          @contextmenu.prevent="openMultiMenu($event, seg)">{{ seg.text }}</span>
      </template>
      <!-- 右键多标注选择菜单 -->
      <el-dropdown v-if="menu.visible" :style="{ position: 'fixed', left: menu.x + 'px', top: menu.y + 'px' }"
        @close="menu.visible = false">
        <span></span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item v-for="id in menu.seg?.ids || []" :key="id" @click="focusAnno(id)">{{ shortAnnoLabel(id)
              }}</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, onBeforeUnmount, unref, nextTick } from 'vue'
import { useDocStore } from '../store/doc'
import { useAnnoStore } from '../store/anno'
import { useLabelsStore } from '../store/labels'
import { Annotator as PoplarAnnotator } from 'poplar-annotation'
import { ElMessage } from 'element-plus'
import { DocumentCopy } from '@element-plus/icons-vue'

const doc = useDocStore()
const anno = useAnnoStore()
const labels = useLabelsStore()

const textRef = ref<HTMLElement | null>(null)
const textAreaRef = ref<HTMLElement | null>(null)
const rendered = computed(() => anno.renderedSegments(doc.content, labels.byId))

const editable = ref(true)
const textEditMode = ref(false)
const editableContent = ref('')
// 多重标注渲染模式：stripe(条纹区分) | blend(旧的渐变混合)
const multiRenderMode = ref<'stripe' | 'blend'>('stripe')
const poplarRef = ref<HTMLElement | null>(null)
const gutterRef = ref<HTMLElement | null>(null)
let poplar: PoplarAnnotator | null = null
const previewMode = ref<'svg' | 'flow'>('svg')
const pngExporting = ref(false)

function toPoplarData() {
  // 将现有文本与标注转换为 poplar-annotation 的数据结构
  const content0 = unref((doc as any).content)
  const labelMap = unref((labels as any).byIdPlain) as Record<string, any>
  const anns0 = unref((anno as any).list) as any[]
  const content = typeof content0 === 'string' ? content0 : (content0 ?? '')
  const labelCategories = Object.values(labelMap || {}).map((l: any) => ({ id: l.id, text: l.name, color: l.color || undefined }))
  const maxLen = content.length
  const anns = Array.isArray(anns0) ? anns0 : []
  const popLabels = anns
    .map((a: any) => ({
      id: a.id,
      categoryId: a.label_id,
      startIndex: Math.max(0, Math.min(maxLen, Number(a.start_offset) ?? 0)),
      endIndex: Math.max(0, Math.min(maxLen, Number(a.end_offset) ?? 0)),
    }))
    .filter(l => Number.isFinite(l.startIndex) && Number.isFinite(l.endIndex) && l.endIndex > l.startIndex)

  console.log('[toPoplarData] 原始数据:', { content0, anns0, labelMap })
  console.log('[toPoplarData] 处理后:', { content, labelCategories, popLabels })

  return {
    content: content || ' ',
    labelCategories,
    labels: popLabels,
    connectionCategories: [],
    connections: [],
  }
}

async function mountPoplar() {
  if (previewMode.value !== 'svg') return
  if (!poplarRef.value) return
  // 清理
  if (poplar) { 
    // 移除旧 svg 的 resize 监听
    try {
      const oldSvg = poplarRef.value?.querySelector('svg') as any
      if (oldSvg && typeof oldSvg.__stopWidthListener === 'function') oldSvg.__stopWidthListener()
    } catch { }
    poplar.remove(); poplar = null 
  }
  poplarRef.value.innerHTML = ''
  if (gutterRef.value) gutterRef.value.innerHTML = ''
  // 等待有宽度
  let tries = 0
  while (poplarRef.value && poplarRef.value.clientWidth === 0 && tries < 120) {
    await new Promise(r => setTimeout(r, 16))
    tries++
  }
  // 记录容器宽度，后续用于强制 svg 自适应
  const containerWidth = poplarRef.value?.clientWidth || 0
  const data = toPoplarData()
  // 计算嵌套深度，用于后续增强可视化
  const depthMap: Record<string, number> = {}
  const arr = data.labels || []
  for (const a of arr) { depthMap[a.id] = 1 }
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length; j++) {
      if (i === j) continue
      const A = arr[i]; const B = arr[j]
      if (A.startIndex >= B.startIndex && A.endIndex <= B.endIndex) {
        depthMap[A.id] = Math.max(depthMap[A.id], (depthMap[B.id] || 1) + 1)
      }
    }
  }
  try {
    // 使用 monkey patch 在 Poplar 内部创建 svg 前写入理想宽度，保证初始分行基于容器宽度
    const targetWidth = containerWidth
    const SVGNS = 'http://www.w3.org/2000/svg'
    const originalCreate = document.createElementNS
    if (targetWidth > 0) {
      (document as any).createElementNS = function(ns: string, name: string) {
        const el = originalCreate.call(document, ns, name)
        if (ns === SVGNS && name === 'svg') {
          try { el.setAttribute('width', String(targetWidth)) } catch { }
        }
        return el
      }
    }
    try {
      poplar = new PoplarAnnotator(data, poplarRef.value, {
      contentEditable: false,
      lineHeight: 1.6,
      defaultLabelColor: '#ff9d61',
      topContextMargin: 1,
      labelPadding: 2,
      bracketWidth: 2
      })
    } finally {
      // 还原 createElementNS
      ;(document as any).createElementNS = originalCreate
    }
    // 尝试自适应：让外层容器控制宽度，禁止内部强制宽
    const svg = poplarRef.value.querySelector('svg') as SVGSVGElement | null
    if (svg) {
      // 如果初始宽度过小（例如默认 token 宽度或未设置）则设为容器宽度
      try {
        const wAttr = svg.getAttribute('width')
        const wNum = Number(wAttr)
        if (containerWidth > 0 && (!wAttr || !Number.isFinite(wNum) || wNum < containerWidth * 0.9)) {
          svg.setAttribute('width', String(containerWidth))
        }
      } catch (e) { /* ignore */ }
      svg.style.maxWidth = '100%'
      svg.style.height = 'auto'
      svg.style.display = 'block'
      enhanceNested(svg, depthMap)
      buildGutter(svg)
      adjustSvgSize(svg)
      // 再次延迟调整，防止字体渲染延迟导致的高度不足
      setTimeout(() => adjustSvgSize(svg), 60)
      setTimeout(() => adjustSvgSize(svg), 300)
      // 额外：窗口尺寸变化时重新调整（简单节流）
      let resizeTimer: any
      const onResize = () => {
        if (!poplarRef.value) return
        clearTimeout(resizeTimer)
        resizeTimer = setTimeout(() => {
          if (!poplarRef.value) return
            const cw = poplarRef.value.clientWidth
            if (cw > 0) {
              svg.setAttribute('width', String(cw))
              adjustSvgSize(svg)
            }
        }, 120)
      }
      window.addEventListener('resize', onResize)
      // 清理监听
      const stopWatch = () => { window.removeEventListener('resize', onResize) }
      // 挂到 svg 自定义属性以便后续清理
      ;(svg as any).__stopWidthListener = stopWatch
    }
  } catch (e) {
    console.error('[Poplar] init failed', e)
  }
}

watch([() => (doc as any).content?.value ?? (doc as any).content, () => (anno as any).list?.value ?? (anno as any).list], () => {
  if (!editable.value && previewMode.value === 'svg') {
    // 轻微延迟等DOM稳定
    setTimeout(() => mountPoplar(), 50)
  }
}, { deep: true })

watch(editable, (v) => {
  console.log('[watch editable] 切换到:', v ? '标注' : '预览')
  if (!v) {
    setTimeout(() => mountPoplar(), 60)
  } else {
    if (poplar) { poplar.remove(); poplar = null }
  }
})

onMounted(() => { if (!editable.value) mountPoplar() })

onBeforeUnmount(() => { if (poplar) { poplar.remove(); poplar = null } })

function getOffsets(): [number, number] | null {
  const sel = window.getSelection()
  if (!sel || sel.rangeCount === 0) return null
  const range = sel.getRangeAt(0)
  const pre = range.cloneRange()
  pre.selectNodeContents(textRef.value!)
  pre.setEnd(range.startContainer, range.startOffset)
  const start = pre.toString().length
  const end = start + range.toString().length
  if (end === start) return null
  return [start, end]
}

function tryAnnotate() {
  const off = getOffsets()
  const label = labels.current
  if (!off || !label) return
  const [start, end] = off
  const txt = doc.content.slice(start, end)
  anno.addAnnotation({ start_offset: start, end_offset: end, text: txt, label_id: label.id })
}

function focusAnno(id: string) {
  anno.focus(id)
}

// 多重标注的样式：支持两种模式，默认 stripe 保留原色不失真
function hlStyle(seg: any) {
  if (!seg.multi) {
    return { backgroundColor: seg.colors?.[0] || '#ffec99' }
  }
  const colors: string[] = seg.colors || []
  if (multiRenderMode.value === 'blend') {
    const layers = colors.map((c: string, i: number) => {
      const alpha = 0.55 // 固定透明度，避免越多越脏
      const rgba = hexToRgba(c, alpha)
      return `linear-gradient(${90 + i * 12}deg, ${rgba}, ${rgba})`
    })
    return { backgroundImage: layers.join(','), backgroundBlendMode: 'multiply' }
  }
  // stripe 模式：均分宽度条纹
  const n = colors.length
  const stops: string[] = []
  for (let i = 0; i < n; i++) {
    const start = (i / n * 100).toFixed(2) + '%'
    const end = ((i + 1) / n * 100).toFixed(2) + '%'
    stops.push(`${colors[i]} ${start} ${end}`)
  }
  const gradient = `linear-gradient(90deg, ${stops.join(',')})`
  return {
    backgroundImage: gradient,
    backgroundColor: 'transparent',
    outline: '1px solid rgba(0,0,0,0.15)',
    boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.25)'
  }
}

function hexToRgba(hex: string, a = 1) {
  if (!hex) return `rgba(255,157,97,${a})`
  const h = hex.replace('#', '')
  if (h.length === 3) {
    const r = parseInt(h[0] + h[0], 16)
    const g = parseInt(h[1] + h[1], 16)
    const b = parseInt(h[2] + h[2], 16)
    return `rgba(${r},${g},${b},${a})`
  }
  if (h.length >= 6) {
    const r = parseInt(h.slice(0, 2), 16)
    const g = parseInt(h.slice(2, 4), 16)
    const b = parseInt(h.slice(4, 6), 16)
    return `rgba(${r},${g},${b},${a})`
  }
  return `rgba(255,157,97,${a})`
}

// 右键菜单管理多重标注
const menu = ref<{ visible: boolean, x: number, y: number, seg: any | null }>({ visible: false, x: 0, y: 0, seg: null })
function openMultiMenu(e: MouseEvent, seg: any) {
  if (!seg.multi) return
  menu.value = { visible: true, x: e.clientX, y: e.clientY, seg }
}
function shortAnnoLabel(id: string) {
  const a = anno.list.find(i => i.id === id)
  if (!a) return id
  const l = (labels as any).byId?.[a.label_id]
  const name = l?.name || '标签'
  return `${name} (${a.start_offset}-${a.end_offset})`
}

// ---------- SVG 增强：嵌套可视 + 行号 / 导出 ----------
function enhanceNested(svg: SVGSVGElement, depthMap: Record<string, number>) {
  // 通过多选择器尝试找到 label 元素（容错）
  Object.entries(depthMap).forEach(([id, depth]) => {
    const selectorCandidates = [
      `[data-id='${id}']`, `[data-label-id='${id}']`, `g[data-id='${id}']`, `g[label-id='${id}']`, `*[data-id='${id}']`
    ]
    let el: Element | null = null
    for (const sel of selectorCandidates) {
      el = svg.querySelector(sel)
      if (el) break
    }
    if (!el) return
    // 找到形状元素
    let shape: Element | null = el.querySelector('rect') || el.querySelector('path') || el
    if (!shape) return
    const strokeWidth = Math.min(6, 1 + (depth - 1) * 1.2)
      ; (shape as any).style.stroke = 'rgba(0,0,0,0.25)'
      ; (shape as any).style.strokeWidth = strokeWidth
      ; (shape as any).style.strokeLinejoin = 'round'
    // 内部再加一层亮色描边以区分层次
    if (depth > 1) {
      ; (shape as any).style.filter = 'drop-shadow(0 0 2px rgba(0,0,0,0.25))'
        ; (shape as any).style.opacity = Math.max(0.35, 0.85 - depth * 0.08)
    }
  })
}

function buildGutter(svg: SVGSVGElement) {
  if (!gutterRef.value) return
  gutterRef.value.innerHTML = ''
  // 通过 text 元素的 y 值推断行
  const texts = Array.from(svg.querySelectorAll('text')) as SVGTextElement[]
  if (texts.length === 0) return
  const ys: number[] = []
  texts.forEach(t => {
    try {
      const y = parseFloat(t.getAttribute('y') || 'NaN')
      if (isFinite(y)) ys.push(y)
    } catch { }
  })
  if (ys.length === 0) return
  ys.sort((a, b) => a - b)
  // 去重(容差)
  const linePositions: number[] = []
  const tolerance = 1
  ys.forEach(y => {
    if (!linePositions.some(p => Math.abs(p - y) < tolerance)) linePositions.push(y)
  })
  const first = linePositions[0]
  const last = linePositions[linePositions.length - 1]
  const height = last - first + 20
  gutterRef.value.style.height = height + 'px'
  linePositions.forEach((y, i) => {
    const div = document.createElement('div')
    div.className = 'g-line'
    div.textContent = String(i + 1)
    div.style.top = (y - first) + 'px'
    gutterRef.value!.appendChild(div)
  })
}

function rebuildPoplar() {
  setTimeout(() => mountPoplar(), 10)
}

function exportCurrentSvg() {
  if (!poplarRef.value) return
  const svg = poplarRef.value.querySelector('svg')
  if (!svg) return
  const blob = new Blob(['<?xml version="1.0" encoding="UTF-8"?>\n' + svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'annotation.svg'
  a.click()
  URL.revokeObjectURL(url)
}

async function exportCurrentPng() {
  if (!poplarRef.value) return
  const svg = poplarRef.value.querySelector('svg') as SVGSVGElement | null
  if (!svg) return
  pngExporting.value = true
  try {
    const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + svg.outerHTML
    const svgBlob = new Blob([xml], { type: 'image/svg+xml;charset=utf-8' })
    const url = URL.createObjectURL(svgBlob)
    const img = new Image()
    await new Promise<void>((res, rej) => {
      img.onload = () => res()
      img.onerror = rej
      img.src = url
    })
    const canvas = document.createElement('canvas')
    const bb = svg.getBBox()
    canvas.width = Math.ceil(bb.width + 16)
    canvas.height = Math.ceil(bb.height + 16)
    const ctx = canvas.getContext('2d')!
    ctx.fillStyle = '#fff'
    ctx.fillRect(0, 0, canvas.width, canvas.height)
    ctx.drawImage(img, 8, 8)
    URL.revokeObjectURL(url)
    const pngUrl = canvas.toDataURL('image/png')
    const a = document.createElement('a')
    a.href = pngUrl
    a.download = 'annotation.png'
    a.click()
  } catch (e) {
    console.error('PNG 导出失败', e)
  } finally {
    pngExporting.value = false
  }
}

function adjustSvgSize(svg: SVGSVGElement) {
  try {
    // 移除可能存在的 viewBox 限制
    svg.removeAttribute('viewBox')
    svg.style.overflow = 'visible'
    // 计算所有可见元素包围盒
    const elements = Array.from(svg.querySelectorAll('*')) as Element[]
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
    for (const el of elements) {
      const tag = el.tagName.toLowerCase()
      if (['defs', 'style', 'clipPath', 'marker'].includes(tag)) continue
      const anyEl: any = el
      if (typeof anyEl.getBBox !== 'function') continue
      let bb; try { bb = anyEl.getBBox() } catch { continue }
      if (!bb || bb.width === 0 && bb.height === 0) continue
      if (!isFinite(bb.x) || !isFinite(bb.y)) continue
      minX = Math.min(minX, bb.x)
      minY = Math.min(minY, bb.y)
      maxX = Math.max(maxX, bb.x + bb.width)
      maxY = Math.max(maxY, bb.y + bb.height)
    }
    if (!(isFinite(minX) && isFinite(maxX) && maxX > minX)) return
    const pad = 12
    const w = Math.ceil(maxX - minX + pad * 2)
    const h = Math.ceil(maxY - minY + pad * 2)
    svg.setAttribute('width', String(w))
    svg.setAttribute('height', String(h))
    // 用 transform 统一平移，让内容不被裁剪
    let rootGroup = svg.querySelector('g') as SVGGElement | null
    if (rootGroup) {
      const old = rootGroup.getAttribute('transform') || ''
      // 避免重复叠加
      if (!/translate\(/.test(old)) {
        rootGroup.setAttribute('transform', `translate(${-minX + pad}, ${-minY + pad}) ${old}`.trim())
      }
    }
  } catch (e) {
    console.warn('adjustSvgSize failed', e)
  }
}

// 文本编辑模式相关函数
function toggleTextEditMode() {
  textEditMode.value = !textEditMode.value
  if (textEditMode.value) {
    // 进入编辑模式时，将当前文档内容复制到编辑器
    editableContent.value = doc.content
    // 清空标注（因为要重新编辑文本）
    if (editableContent.value !== doc.content) {
      anno.clearAll()
    }
  } else {
    // 退出编辑模式时，更新文档内容
    updateContent()
  }
}

function updateContent() {
  if (editableContent.value !== doc.content) {
    doc.setContent(editableContent.value)
    // 文本变更时清空所有标注
    anno.clearAll()
  }
}

function handleKeyDown(event: KeyboardEvent) {
  // 处理 Ctrl+V 粘贴
  if (event.ctrlKey && event.key === 'v') {
    // 让默认的粘贴行为发生，然后在下一个tick更新内容
    nextTick(() => {
      updateContent()
    })
  }
}

async function pasteFromClipboard() {
  try {
    // 检查剪贴板API是否可用
    if (!navigator.clipboard) {
      ElMessage.warning('您的浏览器不支持剪贴板API，请手动粘贴')
      return
    }

    // 读取剪贴板内容
    const text = await navigator.clipboard.readText()
    if (text) {
      // 如果当前有内容，询问是否替换
      if (editableContent.value.trim()) {
        // 可以在这里添加确认对话框
        const shouldReplace = confirm('检测到剪贴板有内容，是否替换当前文本？')
        if (!shouldReplace) {
          // 追加到当前内容
          editableContent.value += '\n' + text
        } else {
          editableContent.value = text
        }
      } else {
        editableContent.value = text
      }

      updateContent()
      ElMessage.success('已从剪贴板粘贴内容')
    } else {
      ElMessage.warning('剪贴板为空')
    }
  } catch (error) {
    console.error('读取剪贴板失败:', error)
    ElMessage.error('读取剪贴板失败，请检查浏览器权限或手动粘贴')
  }
}

// 初始化编辑内容
watch(() => doc.content, (newContent) => {
  if (!textEditMode.value) {
    editableContent.value = newContent
  }
}, { immediate: true })
</script>
<style scoped>
.annotator {
  font-family: system-ui, sans-serif;
  line-height: 1.7;
}

.text {
  white-space: pre-wrap;
}

.preview-text {
  white-space: pre-wrap;
  line-height: 1.7;
}

.preview-wrapper {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.preview-mode-toggle {
  text-align: right;
}

.hl {
  border-radius: 3px;
  padding: 0 2px;
  cursor: pointer;
}

.toolbar {
  margin-bottom: 8px;
  display: flex;
  gap: 8px;
  align-items: center;
}

.text-edit-mode {
  flex: 1 1 auto;
  width: 100%;
}

.text-edit-mode .el-textarea {
  height: 100%;
}

.text-edit-mode .el-textarea__inner {
  height: 100% !important;
  resize: none;
  font-family: system-ui, sans-serif;
  line-height: 1.7;
}

.annotator {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.poplar-wrap {
  border: 1px solid #eee;
  border-radius: 6px;
  padding: 8px;
  flex: 1 1 auto;
  width: 100%;
  box-sizing: border-box;
  overflow: auto;
}

.poplar-container {
  width: 100%;
  min-height: 100%;
}

.poplar-container>svg {
  width: auto;
  height: auto;
  display: block;
}

#app .poplar-annotation-label {
  font-size: 12px;
}

#app .poplar-annotation-content {
  font-size: 14px;
  line-height: 1.4;
}
</style>
